﻿using System;
using System.Collections.Generic;
using System.Linq;
using BoldBrick.Core.Logging;
using BoldBrick.SharePoint.CommonControls.Extensions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace BoldBrick.SharePoint.CommonControls.DataObjects.Base
{
	/// <summary>
	/// Base metadata class for <see cref="SPContentType"/> object
	/// </summary>
	public abstract class BaseContentType : BaseDataObject
	{
		private static readonly ILogger log = Logger.GetLogger(typeof(BaseContentType));

		#region Properties

		private SPContentTypeId id = SPContentTypeId.Empty;

		/// <summary>
		/// ID (vector) of the content type
		/// </summary>
		public SPContentTypeId ID
		{
			get
			{
				if (id == SPContentTypeId.Empty)
					id = new SPContentTypeId(GetContentTypeID());

				return id;
			}
		}

		#endregion

		#region ------ Metadata Methods -----------------------------------------------------------

		/// <summary>
		/// Gets content type ID in string format
		/// </summary>
		/// <returns>Returns content type ID in string format</returns>
		protected abstract string GetContentTypeID();

		/// <summary>
		/// Gets name of the content type
		/// </summary>
		/// <param name="language">Language identifier</param>
		/// <returns></returns>
		public virtual string GetName(uint language)
		{
			string source = string.Format("$Resources: {0}", GetNameResourceKey());
			return SPUtility.GetLocalizedString(source, this.ResourceFileName, language);
		}

		/// <summary>
		/// Gets resource key for name of the content type
		/// </summary>
		/// <returns>Returns resource key for name of the content type</returns>
		protected virtual string GetNameResourceKey()
		{
			throw new NotImplementedException("Implement 'GetNameResourceKey' method in inherited class or override 'GetName' method.");
		}


		/// <summary>
		/// Gets group name of the content type
		/// </summary>
		/// <param name="language">Language identifier</param>
		/// <returns>Returns group name of the content type</returns>
		public virtual string GetGroupName(uint language)
		{
			string source = string.Format("$Resources: {0}", GetGroupResourceKey());
			return SPUtility.GetLocalizedString(source, this.ResourceFileName, language);
		}

		/// <summary>
		/// Gets resource key for group name of the content type
		/// </summary>
		/// <returns>Returns resource key for group name of the content type</returns>
		protected virtual string GetGroupResourceKey()
		{
			throw new NotImplementedException("Implement 'GetGroupResourceKey' method in inherited class or override 'GetGroupName' method.");
		}


		/// <summary>
		/// Gets description of the content type
		/// </summary>
		/// <param name="language">Language identifier</param>
		/// <returns>Returns description of the content type</returns>
		public virtual string GetDescription(uint language)
		{
			string nameResourceKey = GetDescriptionResourceKey();
			if (string.IsNullOrEmpty(nameResourceKey))
				return null;

			string source = string.Format("$Resources: {0}", nameResourceKey);
			return SPUtility.GetLocalizedString(source, this.ResourceFileName, language);
		}

		/// <summary>
		/// Gets resource key for description of the content type
		/// </summary>
		/// <returns>Returns resource key for description of the content type</returns>
		protected virtual string GetDescriptionResourceKey()
		{
			return null;
		}

		#endregion


		#region Create Methods

		/// <summary>
		/// Creates the content type in web. If content type if already created, updates content type properties
		/// </summary>
		/// <param name="web">Web of the content type</param>
		public void Create(SPWeb web)
		{
			string contentTypeName = this.GetName(web.Language);
			log.Write(LogLevel.Info, "Create '{0}' content type with '{1}' ID in '{1}' web.", contentTypeName, this.ID, web.ServerRelativeUrl);
			SPContentType contentType = web.GetContentType(this.ID);
			if (contentType == null)
			{
				web.AllowUnsafeUpdates = true;

				string name = GetName(web.Language);
				contentType = new SPContentType(this.ID, web.ContentTypes, name);
				web.ContentTypes.Add(contentType);
			}
			else
			{
				log.Write(LogLevel.Info, "'{0}' content type with '{1}' ID already exist in '{2}' web.", contentTypeName, this.ID, web.ServerRelativeUrl);
			}

			contentType.Group = GetGroupName(web.Language);
			string description = GetDescription(web.Language);
			if (description != null)
			{
				contentType.Description = description;
			}

			contentType.Update(true);

			web.Update();

			UpdateFieldLinks(web);
		}

		#endregion

		#region Columns Methods

		/// <summary>
		/// Gets list columns of the content type
		/// </summary>
		/// <returns>Returns list columns of the content type</returns>
		public virtual List<ListColumn> GetListColumns()
		{
			return new List<ListColumn>();
		}

		/// <summary>
		/// Gets ordered list columns of the content type (for display or edit forms)
		/// </summary>
		/// <returns>Returns ordered list columns of the content type (for display or edit forms)</returns>
		public virtual List<ListColumn> GetOrderedListColumns()
		{
			return GetListColumns();
		}

		/// <summary>
		/// Updates field links order based of <see cref="GetOrderedListColumns"/> method definition
		/// </summary>
		/// <param name="contentTypes">Parent collection of the content type</param>
		public void UpdateColumnOrder(SPContentTypeCollection contentTypes)
		{
			log.Write(LogLevel.Info, "Reordering fields in '{0}' content type.", this);

			string[] order = GetOrderedListColumns()
				.Select(column => column.InternalName)
				.ToArray();

			if (order.Length == 0)
			{
				log.Write(LogLevel.Warn, "No columns in defintion for reordering fields in '{0}' content type.", this);
				return;
			}

			SPContentType contentType = contentTypes
				.OfType<SPContentType>()
				.FirstOrDefault(cnt => cnt.Id.Equals(this.ID) || cnt.Id.IsChildOf(this.ID));

			if (contentType == null)
			{
				log.Write(LogLevel.Error, "'{0}' content type not found.", this);
				return;
			}

			contentType.FieldLinks.Reorder(order);
			contentType.Update(true);
		}

		/// <summary>
		/// Updates field links base on <see cref="GetListColumns"/> method definition
		/// </summary>
		/// <param name="web">Parent web of the content type</param>
		public void UpdateFieldLinks(SPWeb web)
		{
			string contentTypeName = GetName(web.Language);
			log.Write(LogLevel.Info, "Update field link for '{0}' content type in '{1}' web.", contentTypeName, web.ServerRelativeUrl);

			SPContentType contentType = web.GetContentType(contentTypeName);
			if (contentType == null)
			{
				string message = string.Format("'{0}' content type does not exist in '{1}' web.",
					contentTypeName,
					web.ServerRelativeUrl);
				log.Write(LogLevel.Error, message);
				throw new Exception(message);
			}

			web.AllowUnsafeUpdates = true;

			IEnumerable<ListColumn> listColumns = this.GetListColumns();

			foreach (ListColumn listColumn in listColumns)
			{
				log.Write(LogLevel.Info, "Try to add '{0}' field link to '{1}' content type in '{2}' web.", listColumn.InternalName, contentTypeName, web.ServerRelativeUrl);
				SPField field = web.AvailableFields.GetFieldByInternalName(listColumn.InternalName);
				if (field == null)
				{
					string message = string.Format("'{0}' field does not exist in '{1}' web.",
						listColumn.InternalName,
						web.ServerRelativeUrl);
					log.Write(LogLevel.Error, message);
					throw new Exception(message);
				}

				SPFieldLink fieldLink = contentType.GetFieldLink(listColumn.InternalName);

				bool updated = false;
				if (fieldLink == null)
				{
					contentType.AddFieldLink(field);
					updated = true;
					fieldLink = contentType.GetFieldLink(listColumn.InternalName);
					log.Write(LogLevel.Info, "'{0}' field link is successfully added to '{1}' content type in '{2}' web.",
						listColumn.InternalName,
						contentTypeName,
						web.ServerRelativeUrl);
				}
				else
				{
					log.Write(LogLevel.Info, "'{0}' field link is already added to '{1}' content type in '{2}' web.",
						listColumn.InternalName,
						contentTypeName,
						web.ServerRelativeUrl);
				}

				updated |= UpdateFieldLink(fieldLink, listColumn, web.Language);
				if (updated)
				{
					contentType.Update(true);
				}
			}
		}

		/// <summary>
		/// Updates field link of this content type
		/// </summary>
		/// <param name="fieldLink">Field link to update</param>
		/// <param name="listColumn"><see cref="ListColumn"/> metadata object with properties</param>
		/// <param name="language">Language identifier</param>
		/// <returns>Returns true if field link is updated, otherwise returns false</returns>
		private static bool UpdateFieldLink(SPFieldLink fieldLink, ListColumn listColumn, uint language)
		{
			bool updated = false;

			string displayName = listColumn.GetDisplayName(language);
			if (fieldLink.DisplayName != displayName)
			{
				fieldLink.DisplayName = displayName;
				updated = true;
			}

			return updated;
		}

		#endregion

		public override string ToString()
		{
			string name = GetName(1033);
			return string.Format("{0} ({1})", name, this.ID);
		}

	}
}
